﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using AnjLab.SyncIT.Core.Api;

namespace AnjLab.SyncIT.Core.Data.DotProject
{
    public class DotProjectUIParser
    {
        private readonly Source _source;

        private string _cookie;

        private string shortDateFormat;
        private string version;

        public DotProjectUIParser(Source source)
        {
            _source = source;
        }

        public ISourceTask ParseTaskURL(string taskURL)
        {
            Library.Log.Debug("Parsing task: " + taskURL);

            string taskHtml = GetHtml(taskURL);

            var task = new Task();

            User owner = ExtractUser(GetSelectedItem(taskHtml, "task_owner"));

            task.Owner = owner.DisplayName;
            task.OwnerID = owner.ID;

            task.Priority = GetSelectedItem(taskHtml, "task_priority").Groups["name"].Value;
            task.Importance = ConvertPriorityIdToImportance(
                GetSelectedItem(taskHtml, "task_priority").Groups["id"].Value);

            task.Progress = int.Parse(GetSelectedItem(taskHtml, "task_percent_complete").Groups["name"].Value);
            
            task.Status = GetSelectedItem(taskHtml, "task_status").Groups["name"].Value;
            task.StatusCode = ConvertStatusIdToStatusCode(GetSelectedItem(taskHtml, "task_status").Groups["id"].Value);

            task.UserID = Regex.Match(taskHtml, "user_id=(?<id>\\d+)").Groups["id"].Value;

            if (version == null)
            {
                version = Regex.Match(taskHtml, "name=\"Version\" content=\"(?<value>[^\"]*)")
                    .Groups["value"].Value;
            }

            if (shortDateFormat == null)
            {
                shortDateFormat = GetShortDateFormat(task.UserID);
            }

            task.ShortDateFormat = shortDateFormat;

            task.ID = Regex.Match(taskHtml, "\\?m=tasks&a=view&task_id=(?<id>\\d+)").Groups["id"].Value;
            task.ProjectName =
                Regex.Match(taskHtml, "id='task_contacts'[.\\s\\S]*<strong>.*:\\s(?<projectName>.*)</strong>")
                    .Groups["projectName"].Value;
            task.ProjectID = Regex.Match(taskHtml, "\\?m=tasks&project_id=(?<id>\\d+)").Groups["id"].Value;

            task.Summary = Regex.Match(taskHtml, "name=\"task_name\" value=\"(?<summary>[^\"]*)\"").Groups["summary"].Value;

            var mc = Regex.Matches(taskHtml, "value=\"(?<id>\\d+).*>(?<name>.*)\\[\\d+%\\]");

            var users = new List<User>();

            foreach (Match match in mc)
            {
                var user = ExtractUser(match);

                users.Add(user);
            }

            task.ResponsibleUsers = users.ToArray();

            task.StartDate = Regex.Match(taskHtml, "id=\"start_date\" value=\"(?<value>[^\"]*)\"").Groups["value"].Value;
            task.EndDate = Regex.Match(taskHtml, "id=\"end_date\" value=\"(?<value>[^\"]*)\"").Groups["value"].Value;

            int commentOffset = taskHtml.IndexOf("wrap=\"virtual\">") + "wrap=\"virtual\">".Length;
            task.Description = taskHtml.Substring(commentOffset, taskHtml.IndexOf("</textarea>", commentOffset) - commentOffset).Trim();

            task.Contacts = Regex.Match(taskHtml, "id='task_contacts'.*value=\"(?<value>.*)\"").Groups["value"].Value;
            task.Milestone = Regex.Match(taskHtml, "name=\"task_milestone\" checked").Success;

            return task;
        }

        private static Match GetSelectedItem(string taskHtml, string groupName)
        {
            var html = taskHtml.Substring(taskHtml.IndexOf(groupName));

            var match = Regex.Match(html, "value=\"(?<id>\\d+).*selected=\"selected\">(?<name>.*)</option>");

            return match;
        }

        private string GetShortDateFormat(string userID)
        {
            if (_source == null || _source.ProjectBase == null || _source.ProjectBase.StartsWith("file:///"))
            {
                //  Run from testcase
                return "dd/MM/yyyy";
            }

            //  Version 2.1.x see http://code.google.com/p/outlooktrac/issues/detail?id=36
            string settingsUrl = _source.ProjectBase + "/index.php?m=system&a=addeditpref&user_id=" + userID;

            Library.Log.Debug("dotProject settings url resolved to: {0}", settingsUrl);

            var phpFormat = ParseShortDateFormat(settingsUrl);

            return ConvertPhpToDotNetDateFormat(phpFormat);
        }

        public static string ConvertPhpToDotNetDateFormat(string phpFormat)
        {
            //  День числом - dd
            //  Месяц числом - MM
            //  Год числом - yyyy
            //  Месяц прописью (сокращенно) - MMM

            var result = phpFormat.Replace("%d", "dd").Replace("%m", "MM").Replace("%Y", "yyyy").Replace("%b", "MMM");

            return result;
        }

        public string ParseShortDateFormat(string settingsUrl)
        {
            var settingsHtml = GetHtml(settingsUrl);

            int idx = settingsHtml.IndexOf("pref_name[SHDATEFORMAT]");

            if (idx < 0)
            {
                const string defaultShortDateFormat = "%m/%d/%Y";

                Library.Log.Error("Couldn't determine dotProject's Short Date Format. Using default: {0}", defaultShortDateFormat);

                return defaultShortDateFormat;
            }

            var shortDateSettingsHtml = settingsHtml.Substring(idx);

            var result = Regex.Match(shortDateSettingsHtml, "option value=\"(?<value>[^\"]*)\".*selected").Groups["value"].Value;

            return result;
        }

        private static StatusCode ConvertStatusIdToStatusCode(string id)
        {
            return "-1".Equals(id) ? StatusCode.Inactive : StatusCode.Active;
        }

        public static string ConvertStatusCodeToStatusId(StatusCode code)
        {
            return code == StatusCode.Inactive ? "-1" : "0";
        }

        private static Importance ConvertPriorityIdToImportance(string id)
        {
            switch(id)
            {
                case "-1":
                    return Importance.Low;
                case "1":
                    return Importance.High;
                default:
                    return Importance.Normal;
            }
        }

        internal static string ConvertImportanceToPriorityId(Importance importance)
        {
            switch(importance)
            {
                case Importance.High:
                    return "1";
                case Importance.Low:
                    return "-1";
                default:
                    return "0";
            }
        }

        private static User ExtractUser(Match match)
        {
            string[] nameParts = match.Groups["name"].Value.Split(',');
            string firstName;
            string lastName = string.Empty;

            if (nameParts.Length == 2)
            {
                firstName = nameParts[1].Trim();
                lastName = nameParts[0].Trim();
            }
            else
            {
                firstName = match.Groups["name"].Value;
            }

            return new User
                       {
                           ID = match.Groups["id"].Value,
                           FirstName = firstName,
                           LastName = lastName,
                       };
        }

        public ISourceTask ParseTask(string id)
        {
            return ParseTaskURL(_source.ProjectBase + "/index.php?m=tasks&a=addedit&task_id=" + id);
        }

        public string[] ParseMyTODOList(string taskListURL)
        {
            Library.Log.Debug("Parsing my todo list: " + taskListURL);

            string taskListHtml = GetHtml(taskListURL);
            
            if (version == null)
            {
                version = Regex.Match(taskListHtml, "name=\"Version\" content=\"(?<value>[^\"]*)")
                    .Groups["value"].Value;
            }

            Library.Log.Debug("dotProject version " + version);

            var result = new List<string>();

            MatchCollection mc = Regex.Matches(taskListHtml, "/index.php\\?m=tasks&a=view&task_id=(?<id>\\d+)");

            Library.Log.Debug("Found " + mc.Count + " tasks");

            foreach (Match match in mc)
            {
                result.Add(match.Groups["id"].Value);
            }

            return result.ToArray();
        }

        public string[] ParseMyTODOList()
        {
            return ParseMyTODOList(_source.ProjectBase + "/index.php?m=tasks&a=todo");
        }

        private string GetHtml(string url)
        {
            if (_cookie != null)
            {
                return GetHtmlUsingCookie(url);
            }

            string html = GetLoginHtmlWithCookie(url);

            if (html.Contains("loginform"))
            {
                return GetHtmlAfterLogin(url, html);
            }
            //  file: scheme
            return html;
        }

        private string GetHtmlUsingCookie(string url)
        {
            Library.Log.Debug("Trying to parse dotProject using existing cookie... " + url);

            HttpWebRequest request = GetRequestWithCookie(url);

            return ProcessRequest(request);
        }

        private string ProcessRequest(WebRequest request)
        {
            using (var response = request.GetResponse())
            using (var stream = response.GetResponseStream())
            using (var reader = new StreamReader(stream))
            {
                string setCookieHeader = response.Headers.Get("Set-Cookie");
                if (setCookieHeader != null)
                {
                    _cookie = setCookieHeader.Split(';')[0].Split('=')[1];
                }

                var html = reader.ReadToEnd();
                //  DotProject 2.1.5 returns xhtml, so we need to replace xml special chars with html-ones
                return html.Replace("&amp;", "&");
            }
        }

        private HttpWebRequest GetRequestWithCookie(string url)
        {
            var request = (HttpWebRequest)WebRequest.Create(url);

            request.CookieContainer = new CookieContainer(1);
            request.CookieContainer.Add(new Cookie("dotproject", _cookie, "/", request.Address.Host));
            
            return request;
        }

        private string GetHtmlAfterLogin(string url, string html)
        {
            Library.Log.Debug("Logging in to dotProject and parse results... " + url);

            var request = GetRequestWithCookie(url);

            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";

            var output = request.GetRequestStream();

            using (var writer = new StreamWriter(output))
            {
                string loginValue = Regex.Match(html, "name=\"login\" value=\"(?<login>\\d+)\"").Groups["login"].Value;

                writer.Write(string.Format("login={0}&lostpass=0&redirect=&username={1}&password={2}&login=login",
                                           loginValue, _source.UserName, _source.GetPassword()));
            }

            return ProcessRequest(request);
        }

        private string GetLoginHtmlWithCookie(string url)
        {
            Library.Log.Debug("Obtaining dotProject's cookie and login page...");

            var request = WebRequest.Create(url);

            return ProcessRequest(request);
        }

        internal void Ping()
        {
            string taskListHtml = GetHtml(_source.ProjectBase + "/index.php?m=tasks&a=todo");

            PingMyTodoList(taskListHtml);
        }

        public static void PingMyTodoList(string taskListHtml)
        {
            string userID = Regex.Match(taskListHtml, "user_id=(?<id>\\d+)").Groups["id"].Value;

            if (userID.Equals(string.Empty))
            {
                throw new InvalidOperationException("Invalid dotProject URL or UserName/Password pair");
            }
        }

        public void SendTaskDataBack(Task task)
        {
            var request = GetRequestWithCookie(_source.ProjectBase + "/index.php?m=tasks&project_id=" + task.ProjectID);

            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";

            var output = request.GetRequestStream();

            using (var writer = new StreamWriter(output))
            {
                writer.Write(string.Format("dosql=do_task_aed"
                                           + "&task_id={0}"
                                           + "&task_project={1}"
                                           + "&task_contacts={2}"
                                           + "&task_name={3}"
                                           + "&task_status={4}"
                                           + "&task_priority={5}"
                                           + "&task_percent_complete={6}"
                                           + "&{7}",
                                           task.ID, task.ProjectID, task.Contacts, task.Summary, task.StatusCode,
                                           task.Importance, task.Progress, task.Milestone ? "task_milestone=1" : ""));
            }

            ProcessRequest(request);
        }

    }
}
